/**
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.gitee.huangjianbo.supos.openapi.core.aksk;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.gitee.huangjianbo.supos.openapi.core.errors.ApiClientException;
import io.gitee.huangjianbo.supos.openapi.core.tools.HexUtils;
import io.gitee.huangjianbo.supos.openapi.core.tools.StringUtils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * AK/SK工具类
 *
 * @author tomcat
 * @version 1.0
 */
public final class AKSKGenerator {

    private static final Logger LOGGER = LoggerFactory.getLogger(AKSKGenerator.class);

    private static final String payload = "";

    private static final String HMAC_SHA256 = "HmacSHA256";

    /**
     * 生成AK/SK签名
     * <p>
     * 算法：HMAC-SHA256加密，公式为：HexEncode(HMAC-SHA256(SecretKey, CanonicalRequestString)) <br>
     * 格式：Authorization: Sign [AccessKey]-[SignatureString]
     *
     * @param method      请求方法
     * @param path        请求路径
     * @param contentType 请求内容类型
     * @param headers     请求头
     * @param queryParams 请求Query参
     * @param ak          AccessKey
     * @param sk          SecretKey
     * @return 签名字符串 [AccessKey]-[SignatureString]
     */
    public static String sign(String method, String path, String contentType, Map<String, String> headers,
                                String queryParams, String ak, String sk) {
        String canonicalRequestString = method.toUpperCase() + "\n"
                + path + "\n"
                + contentType + "\n"
                + getCanonicalQueryString(queryParams) + "\n"
                + getCanonicalHeaders(headers) + "\n"
                + payload;

        Mac mac = getInitializedMac(HMAC_SHA256, sk);
        byte[] encoded = mac.doFinal(canonicalRequestString.getBytes(StandardCharsets.UTF_8));
        String signature = ak + "-" + HexUtils.encodeHexString(encoded);

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("================= generating signature with AK/SK =================");
            LOGGER.debug("\n" + canonicalRequestString);
            LOGGER.debug("-------------------------------------------------------------------");
            LOGGER.debug(signature);
            LOGGER.debug("===================================================================");
        }

        return signature;
    }

    /**
     * 生成格式化后的请求头
     * <p>
     * <ul>
     *     <li>1、列举所有的以X-MC-为前缀的请求头</li>
     *     <li>2、将HeaderName进行字母小写处理，并按照字母序（ASCLL码序）进行排序</li>
     *     <li>3、根据排序后的LowerCasedHeaderName组装CanonicalCustomHeaders</li>
     *     <li>4、多个Header之间以“;”进行分隔并串联</li>
     * </ul>
     *
     * @param headers 原始请求头
     * @return 格式化后的请求头
     */
    private static String getCanonicalHeaders(Map<String, String> headers) {
        if (headers != null && !headers.isEmpty()) {
            final List<ParamNameValuePair> pairs = new ArrayList<>();
            headers.entrySet().stream()
                    .filter(kv -> kv.getKey().toUpperCase().startsWith("X-MC-"))
                    .forEach(kv ->
                            pairs.add(new ParamNameValuePair(kv.getKey().toLowerCase(), kv.getValue(), ":")));
            return StringUtils.join(pairs.stream()
                    .sorted(Comparator.comparing(ParamNameValuePair::getName))
                    .collect(Collectors.toList()), ";");
        }
        return "";
    }

    /**
     * 生成格式化后的请求参
     * <p>
     * <ul>
     *     <li>1、列举所有的QueryParams，结构KV形式</li>
     *     <li>2、将K进行字母小写处理，并按照字母序（ASCLL码序）进行排序</li>
     *     <li>3、根据排序后的Key组装CanonicalQueryString</li>
     *     <li>4、注意V不需要排序和小写转换</li>
     * </ul>
     *
     * @param queryParams 原始请求参
     * @return 格式化后的请求参
     */
    private static String getCanonicalQueryString(String queryParams) {
        if (StringUtils.isNotEmpty(queryParams)) {
            String[] params = queryParams.split("&");
            final List<ParamNameValuePair> pairs = new ArrayList<>();
            for (String param : params) {
                String[] nvs = param.split("=");
                ParamNameValuePair pair = new ParamNameValuePair(nvs[0].toLowerCase(), nvs.length > 1 ? nvs[1] : "", "=");
                pairs.add(pair);
            }
            return StringUtils.join(pairs.stream()
                    .sorted(Comparator.comparing(ParamNameValuePair::getName))
                    .collect(Collectors.toList()), "&");
        }
        return "";
    }

    public static Mac getInitializedMac(final String algorithm, final String key) {
        if (StringUtils.isEmpty(key)) {
            throw new ApiClientException("The secret key must be not null", new IllegalArgumentException("Null key"));
        }
        try {
            final SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), algorithm);
            final Mac mac = Mac.getInstance(algorithm);
            mac.init(keySpec);
            return mac;
        } catch (final NoSuchAlgorithmException | InvalidKeyException e) {
            throw new ApiClientException("Fail to create algorithm instance with " + algorithm, new IllegalArgumentException(e));
        }
    }
}
